#! /usr/bin/python

#* ========================================================================= *#
#* ------------------------------------------------------------------------- *#
##
##  \file        scriptlib.py
##  \date        Jully 2011
##  \author      TNick
##
##  \brief        Contains various functions that are used by other scripts
##
##
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @n
## Please read README and COPYING in root folder @n
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @n
##
#* ------------------------------------------------------------------------- *#
#* ========================================================================= *#
##
##
##
##
#*  INCLUDES    ------------------------------------------------------------ *#

import    os
import    time


#*  INCLUDES    ============================================================ *#
##
##
##
##
#*  DATA    ---------------------------------------------------------------- *#

#*  DATA    ================================================================ *#
##
##
##
##
#*  FUNCTIONS    ----------------------------------------------------------- *#



#* ------------------------------------------------------------------------- *#
def    parse_dat_file( s_content_file, l_known_tags, l_output ):
    ''' parse the content of a definition (*.dat) file

    The format of the s_content_file is expected to follow these guidelines:
    - comment starts with a #
    - a comment take a whole line
    - each record starts with a { on its own line
    - each record ends with a } on its own line
    - first entry in a record is returned as it is
    - following entries in form a = b are matched against provided list
    and are returned in that order
    - raw strings start with 3 ' characters; the following lines is recorded
    until same 3 ' are found on a separate line

    l_known_tags is a list of known keywords, in uppercase.

    l_output is a list of list of strings:
    [ [ ID1, Val11, Val12 ], [ ID2, Val21, Val22 ] ]

    Returns the number of entries that it added to the list.


    '''

    # status:
    # 1 - waiting for opening bracket
    # 2 - waiting for first statement (id)
    # 3 - after first statement
    # 4 - raw string
    stat = 1

    # tracks current line number
    line_cnt = 0

    # a record is first cached here before being added to l_output
    l_record = [ "" ]
    for s in l_known_tags:
        l_record.append( "" )


    # index when dealing with multiline entries
    idx_multi = -1

    lines_in = s_content_file

    # iterate in lines of the input
    for ln in lines_in:
        line_cnt = line_cnt + 1
        ln_orig = ln
        ln = ln.strip()
        # print( str( line_cnt ) + ": " + ln )

        # raw strings take everything
        if ( stat == 4 ):
            # ends here?
            if ( ln == "'''" ):
                stat = 3
            elif ( len( l_record[idx_multi] ) == 0 ):
                l_record[idx_multi] = ln_orig
            else:
                l_record[idx_multi] = l_record[idx_multi] + ln_orig
            continue

        # skip empty lines
        if len( ln ) == 0 :
            continue

        # skip comments
        if ln[0] == "#":
            continue
        if ln.startswith( "//" ):
            continue
        if ord(ln[0]) == 239: # BOM marker
            #line_cnt = line_cnt - 2
            continue

        # may start a record
        if ( ln == "{" ):
            # not OK if we're inside a record already
            if ( stat == 1 ):
                stat = 2
            else:
                print( "Error!!! Unexpected curly bracket" )
                print( "line [" + str( line_cnt ) + "]" )
                print( "\n\n" )
                return 0;

        # a record may end here
        elif ( ln == "}" ):
            # a record may end only if in status 3
            if ( stat == 3 ):

                # a record ended here
                l_output.append( l_record )

                # reset to ground state
                stat = 1
                l_record = [ "" ]
                i = 0
                for s in l_known_tags:
                    l_record.append( "" )

            elif ( stat == 1 ):
                print( "Error!!! Unexpected curly bracket" )
                print( "line [" + str( line_cnt ) + "]" )
                print( "\n\n" )
                return 0;
            elif ( stat == 2 ):
                print( "Error!!! Record end without any id inside" )
                print( "line [" + str( line_cnt ) + "]" )
                print( "\n\n" )
                return 0;

        # make sure we're inside a record
        elif ( stat == 1 ):
            print( "Error!!! Text outside block" )
            print( "line [" + str( line_cnt ) + "]" )
            print( "\n\n" )
            return 0;

        # found the id; following entries will be a = b pairs
        elif ( stat == 2 ):
            l_record[0] = ln.upper()
            stat = 3

        # this needs to be a a = b pair
        else: # ( stat >= 3 ):
            l_eq = ln.split( "=", 1 )
            if ( ( len( l_eq ) < 2 ) or ( len( l_eq[0].strip() ) == 0 ) ):
                print( "Error!!! <key = value> type of input expected" )
                print( "line [" + str( line_cnt ) + "]" )
                print( "\n\n" )
                return 0;

            # get the tag and find it in input list
            l_eq[0] = l_eq[0].strip().upper()
            try:
                idx_multi = 1 + l_known_tags.index( l_eq[0] )
            except ValueError:
                print( "Error!!! Tag " + l_eq[0] + " was not recognised!" )
                print( "line [" + str( line_cnt ) + "]" )
                print( "\n\n" )
                return 0;

            # are we dealing with a multiline-raw string?
            l_eq[1] = l_eq[1].strip()
            if ( l_record[idx_multi] != "" ):
                print( "Error!!! Tag " + l_eq[0] + " already has an value!" )
                print( "line [" + str( line_cnt ) + "]" )
                print( "\n\n" )
                return 0;
            elif ( l_eq[1] == "'''" ):
                stat = 4
            else:
                l_record[idx_multi] = l_eq[1]


    # check status at the end
    if ( stat == 1 ):
        return len( l_record )
    elif ( stat == 2 ):
        print( "Error!!! Input ended while in record, waiting for first statement!" )
    elif ( stat == 3 ):
        print( "Error!!! Input ended while in record!" )
    elif ( stat == 4 ):
        print( "Error!!! Input ended while in raw string!" )
    elif ( stat == 4 ):
        print( "Error!!! parse_dat_file - Unknown status!" )


    print( "\n\n" )
    return 0;

#* ========================================================================= *#


#* ------------------------------------------------------------------------- *#
def    read_dat_file( s_path_file, l_known_tags, l_output ):
    ''' read a definition (*.dat) file and parse its content

    The format of the file is expected to follow these guidelines:
    - comment starts with a #
    - a comment take a whole line
    - each record starts with a { on its own line
    - each record ends with a } on its own line
    - first entry in a record is returned as it is
    - following entries in form a = b are matched against provided list
    and are returned in that order

    Returns the number of entries that it added to the list.
    '''


    # read source file
    try:
        f = open( s_path_file, "r" )
        lines = f.readlines()
        f.close()
    except IOError:
        print( "Error!!! Source file could not be read:" )
        print( s_path_file )
        print( "\n\n" )
        return 0

    if ( len( lines ) == 0 ):
        return 0

    if ( parse_dat_file( lines, l_known_tags, l_output ) == 0 ):
        print( "Error!!! Source file could not be read:" )
        print( s_path_file )
        print( "\n\n" )
        return 0

    return len( l_output )

#* ========================================================================= *#

#* ------------------------------------------------------------------------- *#
def        walkDirTree( s_path, l_output, l_pattern = None, b_positive = True, b_dirs_too = False ):
    ''' recursivelly walks the directory tree and adds files to list

    s_path represents he path to be searched
    l_output is the list where to output the files and, optionally, folders
    l_pattern is the file name pattern to match; if empty all files are considered
    b_positive tells if the files that match l_pattern are aded to the
        list or ignored
    b_dirs_too is a switch to decide if directory names are added, too

    Examples:

    lst = []
    walkDirTree( "/tmp", lst )
    - will list the files in folder temp
    - the output is placed inside lst
    - all files are added
    - directories are not added to the list

    lst = []
    walkDirTree( "/tmp", lst, [ ".h", ".hpp", ".cc", ".cpp" ] )
    - will list the files in folder temp
    - the output is placed inside lst
    - files that end in listed extensions will be added, other will be ignored
    - directories are not added to the list

    lst = []
    walkDirTree( "/tmp", lst, [ ".h", ".hpp", ".cc", ".cpp" ], False )
    - will list the files in folder temp
    - the output is placed inside lst
    - files that end in listed extensions will NOT be added; all other files
    will be added
    - directories are not added to the list

    lst = []
    walkDirTree( "/tmp", lst, [], True, True )
    - will list the files in folder temp
    - the output is placed inside lst
    - all files are added
    - directories are added, too

    '''

    for file in [ file for file in os.listdir( s_path ) if not file in [ ".", ".." ] ]:
        nfile = os.path.join( s_path, file )

        # type based prcessing
        if os.path.isdir( nfile ):
            # ignore ghost folders
            if ( file == ".svn" ):
                continue
            if ( file == "externals" ):
                continue
            if ( b_dirs_too ):
                l_output.append( nfile )
            walkDirTree( nfile, l_output, l_pattern, b_positive, b_dirs_too )
        else:
            # if there are no filters add everything
            if ( l_pattern == None ):
                l_output.append( nfile )
            else:
                # positive filtering: only add those that match
                if ( b_positive ):
                    for filt in l_pattern:
                        if ( nfile.endswith( filt ) ):
                            l_output.append( nfile )
                            break
                # negative filtering: add those that don't match
                else:
                    b_add_it = True
                    for filt in l_pattern:
                        if ( nfile.endswith( filt ) ):
                            b_add_it = False
                            break
                    if ( b_add_it ):
                        l_output.append( nfile )


#* ========================================================================= *#

#* ------------------------------------------------------------------------- *#
def        needsUpdate( l_ref, l_comp ):
    '''
    returns True if any file time in l_ref is newer than the ones in l_comp
    '''

    for f_ref in l_ref:
        ref_time = os.path.getmtime( f_ref )
        for f_comp in l_comp:
            comp_time = os.path.getmtime( f_comp )
            if ( ref_time > comp_time ):
                return True
    return False

#* ========================================================================= *#

#* ------------------------------------------------------------------------- *#
def        writeFile( s_path, s_content, s_label ):
    '''
    writes the content to specified file
    '''

    try:
        f = open( s_path, "w" )
        f.write( s_content )
        f.close()
    except IOError:
        print( "Error!!! " + s_label + " file could not be written:" )
        print( s_path )
        print( "\n\n" )
        return False
    return True

#* ========================================================================= *#



if __name__ == "__main__":
    s_path_file = "C:/q/msys/1.0/home/tnick/dev/cad-play-ground/branches/redesign-aug-12/"
    lst = []
    walkDirTree( s_path_file, lst, [ ".h", ".hpp", ".cc", ".cpp" ], True, True )
    for ou in lst:
        print ou


#*  FUNCTIONS    =========================================================== *#
##
##
##
##
#* ------------------------------------------------------------------------- *#
#* ========================================================================= *#
